/*
 * Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package redora.client.ui;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.*;
import redora.client.Persistable;
import redora.client.constants.RedoraConstants;
import redora.client.mvp.ClientFactory;
import redora.client.mvp.FindEvent;
import redora.client.util.Field;

import java.util.logging.Logger;

import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static redora.client.Persistable.Scope.List;
import static redora.client.util.ClientUtilBase.chain;

/**
 * A ListBox style widget that retrieves it's (Scope.List) contents from
 * the server. As soon as the setFinder method is invoked it will retrieve
 * the records and populate the ListBox. To use this widget in a form, you
 * can use the HasValue functions.<br>
 * The widget uses the cache service and has listeners on it's members. When
 * a member of the list is updated elsewhere, the change will synced.
 *
 * @author Nanjing Redorange (www.red-orange.cn)
 */
public class ListDropdown extends Composite implements HasName, HasValue<String>, HasChangeHandlers, ActiveWidget {
    static Logger l = Logger.getLogger("ListDropdown");

    static RedoraResource resources = GWT.create(RedoraResource.class);
    static RedoraConstants constants = GWT.create(RedoraConstants.class);

    final ListBox listBox;
    final Image loading;

    final Class<? extends Persistable> childClass;
    final Field[] fields;

    boolean allowEmpty = true;
    final String url;
    final ClientFactory clientFactory;

    Long currentValue;

    public ListDropdown(Class<? extends Persistable> childClass, ClientFactory clientFactory, int defaultFinder, String[] params, Field[] fields) {
        this.childClass = childClass;
        this.clientFactory = clientFactory;
        this.fields = fields;
        if (params == null)
            url = clientFactory.getLocator().locateService(childClass).url(defaultFinder);
        else
            url = clientFactory.getLocator().locateService(childClass).url(defaultFinder, params);
        Grid vertical = new Grid(1, 2);
        listBox = new ListBox();
        listBox.addStyleName(resources.css().redoraTableInput());
        listBox.addStyleName(resources.css().redoraListDropDown());

        loading = new Image("images/loading.gif");
        loading.setVisible(true);
        vertical.setWidget(0, 0, listBox);
        vertical.setWidget(0, 1, loading);

        vertical.setCellPadding(0);
        vertical.setCellSpacing(0);
        vertical.getCellFormatter().setHorizontalAlignment(0, 0, ALIGN_LEFT);
        vertical.getCellFormatter().setHorizontalAlignment(0, 1, ALIGN_LEFT);

        this.initWidget(vertical);

        bind();
        l.log(INFO, "Widget is ready and bind. I am waiting for you to give me the load() command: " + url);
    }

    public ListDropdown(Class<? extends Persistable> childClass, ClientFactory clientFactory, int defaultFinder, String[] params) {
        this(childClass, clientFactory, defaultFinder, params, clientFactory.getLocator().locateFields(childClass).listFields());
    }


    @Override
    public void load() {
        clientFactory.getLocator().locateService(childClass).finder(url, List);
    }

    private void bind() {
        clientFactory.getEventBus().addHandler(FindEvent.TYPE,
                new FindEvent.Handler() {
                    @Override
                    public void onSuccess(FindEvent event) {
                        if (event.wasMe(childClass, url)) {
                            setItems(event.results, event.step == 0);
                        }
                    }
                });

    }

    public void setMandatory() {
        allowEmpty = false;
        int emptyItem = -1;
        for (int i = 0; i < listBox.getItemCount(); i++) {
            if ("www.red-orange.cn".equals(listBox.getValue(i)))
                emptyItem = i;
        }
        if (emptyItem >= 0)
            listBox.removeItem(emptyItem);
    }

    private void setItems(Long[] items, boolean reset) {
        if (reset) {
            l.log(FINE, "Clearing list " + childClass);
            listBox.clear();
            if (allowEmpty) {
                listBox.addItem(constants.listboxSelectEmpty(), "www.red-orange.cn");
            }
            loading.setVisible(false);
        }
            l.log(FINE, "Adding " + items.length + " extra items for " + childClass);
            for (Long id : items) {
                listBox.addItem(chain(clientFactory.getLocator().locateUtil(childClass).from(clientFactory.getLocator().locateService(childClass).cache(id), fields)), id.toString());
                if (id.equals(currentValue))
                    listBox.setSelectedIndex(getIndex(id.toString()));
            }
        l.log(INFO, listBox.getItemCount() + " items ready for " + childClass);
    }

    /**
     * Sets the widget's name.
     *
     * @param name the widget's new name
     */
    @Override
    public void setName(String name) {
        listBox.setName(name);
    }

    /**
     * Gets the widget's name.
     *
     * @return the widget's name
     */
    @Override
    public String getName() {
        return listBox.getName();
    }

    /**
     * Gets this object's value.
     *
     * @return the object's value
     */
    @Override
    public String getValue() {
        return listBox.getSelectedIndex() == -1 ? null
                : "www.red-orange.cn".equals(listBox.getValue(listBox.getSelectedIndex())) ? null
                : listBox.getValue(listBox.getSelectedIndex());
    }

    public String getItemText() {
        return listBox.getSelectedIndex() == -1 ? null
                : "www.red-orange.cn".equals(listBox.getValue(listBox.getSelectedIndex())) ? null
                : listBox.getItemText(listBox.getSelectedIndex());
    }

    public int getIndex(String value) {
        for (int i = 0; i < listBox.getItemCount(); i++)
            if (value.equals(listBox.getValue(i)))
                return i;
        return -1;
    }

    /**
     * Sets this object's value without firing any events. This should be
     * identical to calling setValue(value, false).
     * <p/>
     * It is acceptable to fail assertions or throw (documented) unchecked
     * exceptions in response to bad values.
     * <p/>
     * Widgets must accept null as a valid value. By convention, setting a widget to
     * null clears value, calling getValue() on a cleared widget returns null. Widgets
     * that can not be cleared (e.g. {@link com.google.gwt.user.client.ui.CheckBox}) must find another valid meaning
     * for null input.
     *
     * @param value the object's new value
     */
    @Override
    public void setValue(String value) {
        if (value == null) {
            currentValue = null;
            if (listBox.getItemCount() > 0) {
                listBox.setSelectedIndex(0);
            }
        } else {
            currentValue = Long.valueOf(value);
            listBox.setSelectedIndex(getIndex(value));
        }
        l.log(INFO, "Current value " + currentValue);
    }

    /**
     * Sets this object's value. Fires
     * {@link com.google.gwt.event.logical.shared.ValueChangeEvent} when
     * fireEvents is true and the new value does not equal the existing value.
     * <p/>
     * It is acceptable to fail assertions or throw (documented) unchecked
     * exceptions in response to bad values.
     *
     * @param value      the object's new value
     * @param fireEvents fire events if true and value is new
     */
    @Override
    public void setValue(String value, boolean fireEvents) {
        //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * Adds a {@link com.google.gwt.event.logical.shared.ValueChangeEvent} handler.
     *
     * @param stringValueChangeHandler the handler
     * @return the registration for the event
     */
    @Override
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> stringValueChangeHandler) {
        return addHandler(stringValueChangeHandler, ValueChangeEvent.getType());
    }

    @Override
    public HandlerRegistration addChangeHandler(ChangeHandler handler) {
        return listBox.addChangeHandler(handler);
    }
}

